package com.hipmarket.platform.security.storage

import com.fasterxml.jackson.databind.ObjectMapper
import com.hipmarket.platform.security.JwtAuthenticationUser
import com.hipmarket.platform.security.JwtAuthenticationUserWrapper
import com.hipmarket.platform.security.exception.InvalidTokenException
import org.springframework.data.redis.core.StringRedisTemplate
import java.util.*
import java.util.concurrent.TimeUnit

class JwtAuthenticationRedisStorage(private val redistTemplate: StringRedisTemplate, private val prefix: String = "redis_token_default", private val cacheDays: Long = 7): JwtAuthenticationStorage() {

    private val objectMapper = ObjectMapper()

    private var cachedCls: Class<out JwtAuthenticationUser>? = null

    override fun findUserByToken(token: String): JwtAuthenticationUser {
        val user = redistTemplate.opsForValue().get(token)?: throw InvalidTokenException()
        val wrapper = objectMapper.readValue(user, JwtAuthenticationUserWrapper::class.java)
        if (Objects.isNull(cachedCls)) {
            val objCls = Class.forName(wrapper.clsName)
            try {
                cachedCls = objCls as Class<out JwtAuthenticationUser>
            } catch (e: ClassCastException) {
                throw InvalidTokenException()
            }
        }
        if (cachedCls!!.name == JwtAuthenticationUser::class.java.name) {
            return wrapper.authentication
        }
        val authentication = objectMapper.convertValue(wrapper.authentication, cachedCls)
        val clazz = authentication::class.java
        val field = clazz.getDeclaredField("userDetails")
        val obj =  objectMapper.convertValue(wrapper.authentication.userDetails, field.type)
        field.isAccessible = true
        field.set(authentication, obj)
        return authentication
    }

    override fun login(user: JwtAuthenticationUser): String {
        val token = createToken(user, prefix)
        val userString = objectMapper.writeValueAsString(JwtAuthenticationUserWrapper(user::class.java.name, user))
        redistTemplate.opsForValue().set(token, userString)
        redistTemplate.expire(token, cacheDays, TimeUnit.DAYS)
        if (Objects.isNull(cachedCls)) {
            cachedCls = user::class.java
        }
        return token
    }

    override fun logout(token: String) {
        if (redistTemplate.hasKey(token)) {
            redistTemplate.delete(token)
        }
    }


}